import javax.rmi.CORBA.Stub;
import java.util.*;


class Student{
    private String id;

    public Student() {
    }

    public Student(String id) {
        this.id = id;
    }


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return Objects.equals(id, student.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    @Override
    public String toString() {
        return "Student{" +
                "id='" + id + '\'' +
                '}';
    }
}
public class Main {
    
    //**** 前k个高频的单词****
    // 需要建立小堆
    // 步骤 先遍历数组 统计每个单词出现的次数
    // 然后再建立小堆 按照TopK问题输出前k个出现次数最多的单词
    public static List<String> topKFrequent(String[] words, int k) {
        // 1.遍历数组统计每个单词出现的频率
        // 统计就用HashMap统计
        Map<String,Integer> map = new HashMap<>();
        for (String s : words) {
            if(map.get(s) == null){
                map.put(s,1);
            }else{
                int val = map.get(s);
                map.put(s,val+1);
            }
        }
        // 2.建立小根堆
        PriorityQueue<Map.Entry<String,Integer>> minHeap = new PriorityQueue<>(
                //在里面给一个构造方法

                // 而且因为里面不是一个Map.Entry的方法
                // 因此需要在里面重写一个Comparator方法 ，写一个比较器
                k, new Comparator<Map.Entry<String, Integer>>() {
            @Override
            public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
                // 需要跟新一下比较器
                if(o1.getValue().compareTo(o2.getValue()) == 0){
                    // 如果这两个出现的频率相等
                    // 那就开始调整key
                    return o2.getKey().compareTo(o1.getKey());
                }
                return o1.getValue().compareTo(o2.getValue()); //接下来就变成小根堆了 // 根据频率建立小根堆

            }
        }
        ); //PriorityQueue 默认是小根堆
        // 里面存放Map.Entry<String,Integer>这个类型

        //3.遍历HashMap 把里面 的数据 放到小根堆
        for (Map.Entry<String,Integer> entry:map.entrySet()){
            if(minHeap.size() < k){
                // 这个说明还没有放满
                minHeap.offer(entry);
            }else {
                // 小根堆放满了k个，下一个entry和堆顶元素比较
                Map.Entry<String,Integer> top = minHeap.peek(); // 先获取一下堆顶元素
                if(top.getValue().compareTo(entry.getValue()) < 0){
                    // 如果堆顶的频率小于 当前entry出现的频率
                    minHeap.poll();//就弹出堆顶元素
                    // 再把entry放进来
                    minHeap.add(entry);
                }else {
                    // 还需要再考虑一个情况，那就是
                    // 当出现相同频率的单词时 需要根据字典顺序来存放单词
                    if(top.getValue().compareTo(entry.getValue()) == 0){
                        // 接下来就比较字母
                        if(top.getKey().compareTo(entry.getKey()) > 0){
                            // 谁小把谁放进来
                            //然后再弹再放
                            minHeap.poll();
                            minHeap.add(entry);
                        }
                    }
                }
            }
        }
        // 4.此时小根堆当中已经有了顺序
        // 但是要求输出的是由高到底排序
       // System.out.println(minHeap);
        //
        List<String> list = new ArrayList<>();
        for (int i = 0; i < k; i++) {
            String Key =  minHeap.poll().getKey();
            list.add(Key); // 把每一个值都弹出来并且放到这个数组当中
        }
        Collections.reverse(list); // 然后把这个结果反转一下 Collections.reverse 用到这个对象和方法
        System.out.println(list);
        return list;
    }

    public static void main(String[] args) {
        String[] words = {"a","b","c","a","e","d","a","b","e","a","e"};
        topKFrequent(words,3);
    }

    //**** 坏键盘打字 **** (牛客网)

    // 题目输出描述 ：按照发现顺序，在一行中输出坏掉的键。其中英文字母只输出大写，每个坏键只输出一次。题目保证至少有1个坏键。

    public static void func(String str1,String str2){
        Set<Character> set = new HashSet<>();
        // 先遍历一边str2

        // for(int i = 0; i < str2.length(); i++){
        //     char ch = str2.charAt(i);
        //     set.add(ch);
        // }

        // 改正遍历
        for(char ch:str2.toUpperCase().toCharArray()){
            // 因为要输出的是大写字母，所以在存放的时候就直接转变成大写字母
            // 并且存到数组里面
            set.add(ch);
        }

        // 接下来开始遍历第二个字符串，看看第二个字符串中哪一个字符没有
        // 在set里面出现

        // 再申请一个集合存放坏的键 为了防止重复
        Set<Character> setBroken = new HashSet<>();
        // 更改遍历
        for(char ch:str1.toUpperCase().toCharArray()){
            if(!set.contains(ch) && !setBroken.contains(ch)){
                setBroken.add(ch);
                System.out.print(ch); // 不报含说明是坏的键
                // 但是这个结果还要再处理一下
                // 因为会有重复的

            }
        }
    }
    public static void main21(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextLine()) { // 注意 while 处理多个 case
            String str1 = in.nextLine();
            String str2 = in.nextLine();
            func(str1,str2);
        }
    }


    //**** 宝石与石头 ****
    public int numJewelsInStones(String jewels, String stones) {
        // 思路就是先把 jewels 里面的值放到set里面

        // 定义一个计数器
        int count = 0;
        Set<Character> set = new HashSet<>(); //不用TreeSet
        // 接下来遍历 jewels
        for (int i = 0; i < jewels.length(); i++) {
            char ch = jewels.charAt(i);
            set.add(ch); // 这个循环走完之后说明这个集合里面存放的都是宝石
        }
        // 接下来就去遍历stones
        for (int i = 0; i < stones.length(); i++) {
            char ch = stones.charAt(i); //先拿到一个石头
            // 接下来判断呢个集合里面是否包含ch
            if(set.contains(ch)){
                count++;
            }
        }
        return count;
    }
    class Node {
        int val;
        Node next; // 这是指向下一个地址的指针
        Node random; // 这是一个随机指针，这个值呢有可能随机其他的也有可能随机自己的也有可能随机null

        public Node(int val) {
            this.val = val;
            this.next = null;
            this.random = null;
        }

    // **** 随机链表的复制****
    public Node copyRandomList(Node head) {
        Map<Node,Node> map = new HashMap<>();
        // 第一步先遍历原来的链表
        // 把对应关系存起来
        Node cur = head;
        while(cur != null){
            Node node = new Node(cur.val); // K 的值是 cur.val
            map.put(cur,node); //开始存放 K 的值是老的节点 V的值是新的节点
            cur = cur.next; // cur往后走
        }
        // 接下来再次循环遍历 cur需要再次回到头节点的地方
        cur = head;
        while (cur != null){
            // 接下来开始让每一个新节点的值的 next random 的值都改变
            map.get(cur).next = map.get(cur.next);
            map.get(cur).random = map.get(cur.random);
            cur =cur.next;
        }
        return map.get(head);
    }


        // **** 只出现一次的数字 ****
        public int singleNumber(int[] array) {
            // 用Set和Map的方法来做
            Set<Integer> set = new TreeSet<>();
            for (int i = 0; i < array.length; i++) {
                if(!set.contains(array[i])){
                    set.add(array[i]);
                }else {
                    set.remove(array[i]);
                }
            }
            // 需要再遍历一边来找出这个值
            for (int i = 0; i < array.length; i++) {
                if (set.contains(array[i])){
                    return array[i];
                }
            }
            return -1;
        }

    public static void main(String[] args) {
        // 搜索树的题
        // 第一题： 假如有10个数据，并且数据有重复的，如何去重?

        // 答
        int[] array = {1,2,3,4,5,7,8,8,2,3};
        Set<Integer> set = new HashSet<>();
        for (int x: array) {
            set.add(x); // 因为set本身就是去重的
        }
        System.out.println(set);

        //第二题： 假如有10个数据，并且数据有重复的，那么如何找到第一个重复的数据？
        Set<Integer> set2 = new HashSet<>();
        for (int x: array) {
            if(!set2.contains(x)){ //contains(x)这个是判断集合里面是否有x
                set2.add(x); //如果里面没有x那就增加x
            }else {
                System.out.println(x); //如果里面有x了呢就打印并且返回
                return;
            }
        }

        // 第三题：统计10W个数据中，重复数据出现的次数？

        // 这就需要用到K-V模型了
        // 遍历数组 ： x -> map 有了就val+1，没有：val就是 1；
        // 最后再遍历map
        Map<Integer,Integer> map = new HashMap<>();
        for (int x: array) {
            if(map.get(x) == null){
                map.put(x,1); // 先判断当前的x在map中是否存在过，如果为null就放入x，并且值为1
            }else {
                int val = map.get(x); // 如果有了那就获取原来这个x的val
                map.put(x,val+1); // 并且让val+1
            }
        }
        // 接下来在map里面筛选重复元素
        for (Map.Entry<Integer,Integer> entry : map.entrySet()) { //map.entrySet()表示map里面的每一个元素
            //而entrySet()需要Map.Entry<,>来表示
            if(entry.getValue() > 1){
                System.out.println("key: "+entry.getKey()+" val:"+entry.getValue());
            }

        }
    }
    public static void main12(String[] args) {
        // 刚刚存放的是整型类型的数据那如果要算的是引用类型的数据呢
        Student student1 = new Student("1234"); // 那需要把这引用类型的数据换成整型类型的数据
        // 用到的是hashCode方法
        System.out.println(student1.hashCode()); // 变成一个整数

        // hashCode()是由接口实现的，我们并没有对他进行实现

        Student student2 = new Student("1234");
        System.out.println(student2.hashCode()); // 我们认为 student1 和 student2 是一样的
        // 但是在接下来生成的哈希编码中 结果不一样
        // 那么该如何解决呢
        // 要用自己的方法去实现一个 equals方法并且要重写hashCode方法
        // 这时候这两个哈希码就一样了
        // 这样就可以写一个泛型的hashCode了

        // 测试
        HashBuck2<Student,String> hashBuck2 = new HashBuck2<>();
        hashBuck2.put(student1,"hh");
        String val =  hashBuck2.get(student2); //此时用 student2 来接收
        // 我们就认为 student1 和 student2 是同一个对象
        System.out.println(val);

        // ***** 有一个面试问题
        // 如果 hashCode 一样 ， 那么 equals 一定一样么？
        // 答： 不一定
        // 因为hashCode 一样 只能代表我们找的位置一样
        // 并不能保证我们找的呢个位置的值也是一样的

        // 那如果 equals 一样 ， hashCode 一定一样么？
        // 答： 一定
        // 那么这个就是说明一定是一样的


    }
    public static void main11(String[] args) {
        // 编写一个 数组+链表形式的表
        HashBuck hashBuck = new HashBuck();
        hashBuck.put(1,11);
        hashBuck.put(2,22);
        hashBuck.put(5,55);
        hashBuck.put(8,88);
        hashBuck.put(3,33);
        hashBuck.put(14,144);
        hashBuck.put(6,66);
        hashBuck.put(7,77);

        Integer val =  hashBuck.get(14);
        System.out.println(val);
    }
    public static void main10(String[] args) {
        // ***** 5. 哈希表 *****

        // 5.1 概念
        //顺序结构以及平衡树中，元素关键码与其存储位置之间没有对应的关系，因此在查找一个元素时，必须要经过关键
        //码的多次比较。顺序查找时间复杂度为O(N)，平衡树中为树的高度，即O(logN)，搜索的效率取决于搜索过程中
        //元素的比较次数。

        //理想的搜索方法：可以不经过任何比较，一次直接从表中得到要搜索的元素。 如果构造一种存储结构，通过某种函
        //数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系，那么在查找时通过该函数可以很快
        //找到该元素。

        //当向该结构中：
        //插入元素
        //根据待插入元素的关键码，以此函数计算出该元素的存储位置并按此位置进行存放
        //搜索元素
        //对元素的关键码进行同样的计算，把求得的函数值当做元素的存储位置，在结构中按此位置取元素比较，若关键码相等，则搜索成功
        //该方式即为哈希(散列)方法，哈希方法中使用的转换函数称为哈希(散列)函数，构造出来的结构称为哈希表(HashTable)(或者称散列表)

        //例如：数据集合{1，7，6，4，5，9}；
        //哈希函数设置为：hash(key) = key % capacity; capacity为存储元素底层空间总的大小。

        // 哈希冲突就是两个 不同的key 通过哈希函数 映射到了同一个位置这样就发生了冲突
        //5.2 冲突-概念
        //对于两个数据元素的关键字ki和kj (i != j)，有 ki!=kj ，但有：Hash(ki) == Hash(kj)，即：不同关键字通过相同哈
        //希哈数计算出相同的哈希地址，该种现象称为哈希冲突或哈希碰撞。
        //把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。

        // 所以 哈希表其实就是一个数组 只不过是你要存入的元素和数组的位置有了一个映射关系

        //5.3 冲突-避免
        //首先，我们需要明确一点，由于我们哈希表底层数组的容量往往是小于实际要存储的关键字的数量的，这就导致一
        //个问题， 冲突的发生是必然的 ，但我们能做的应该是尽量的降低冲突率。
        //5.4 冲突-避免-哈希函数设计
        //引起哈希冲突的一个原因可能是：哈希函数设计不够合理。 哈希函数设计原则：
        // 哈希函数的定义域必须包括需要存储的全部关键码，而如果散列表允许有m个地址时，其值域必须在0到m-1之间
        // 哈希函数计算出来的地址能均匀分布在整个空间中
        // 哈希函数应该比较简单
        //一般不会让我们自己去设计一个哈希函数，都是让我们用库当中的

        //常见哈希函数
        //****1. 直接定制法--(常用)****
        //取关键字的某个线性函数为散列地址：Hash（Key）= A*Key + B
        // 优点：简单、均匀
        // 缺点：需要事先知道关键字的分布情况
        // 使用场景：适合查找比较小且连续的情况 面试题：
        //****2. 除留余数法--(常用)****
        //字符串中第一个只出现一次字符
        //设散列表中允许的地址数为m，取一个不大于m，但最接近或者等于m的质数p作为除数，按照哈希函数：
        //Hash(key) = key% p(p<=m),将关键码转换成哈希地址
        //3. 平方取中法--(了解)
        //假设关键字为1234，对它平方就是1522756，抽取中间的3位227作为哈希地址； 再比如关键字为4321，对
        //它平方就是18671041，抽取中间的3位671(或710)作为哈希地址 平方取中法比较适合：不知道关键字的分
        //布，而位数又不是很大的情况
        //比特就业课
        //4. 折叠法--(了解)
        //折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些)，然后将这几部分叠加求和，
        //并按散列表表长，取后几位作为散列地址。
        //折叠法适合事先不需要知道关键字的分布，适合关键字位数比较多的情况
        //5. 随机数法--(了解)
        //选择一个随机函数，取关键字的随机函数值为它的哈希地址，即H(key) = random(key),其中random为随机数函数。
        //通常应用于关键字长度不等时采用此法
        //6. 数学分析法--(了解)
        //设有n个d位数，每一位可能有r种不同的符号，这r种不同的符号在各位上出现的频率不一定相同，可能在某
        //些位上分布比较均匀，每种符号出现的机会均等，在某些位上分布不均匀只有某几种符号经常出现。可根据
        //散列表的大小，选择其中各种符号分布均匀的若干位作为散列地址。例如：
        //假设要存储某家公司员工登记表，如果用手机号作为关键字，那么极有可能前7位都是 相同的，那么我们可以
        //选择后面的四位作为散列地址，如果这样的抽取工作还容易出现 冲突，还可以对抽取出来的数字进行反转(如
        //1234改成4321)、右环位移(如1234改成4123)、左环移位、前两数与后两数叠加(如1234改成12+34=46)等方
        //法。
        //数字分析法通常适合处理关键字位数比较大的情况，如果事先知道关键字的分布且关键字的若干位分布较均
        //匀的情况
        //注意：哈希函数设计的越精妙，产生哈希冲突的可能性就越低，但是无法避免哈希冲突

        //****5.5 冲突-避免-负载因子调节（重点掌握）*****

        // 散列表的载荷因子定义为a： a = 填入表中的元素个数 / 散列表的长度
        // 冲突率和负载因子的关系
        // 负载因子越大冲突率越大

        // ****解决方法就是增加散列表的长度****

        //5.6 冲突-解决
        //解决哈希冲突两种常见的方法是：闭散列和开散列

        //5.7 冲突-解决-闭散列
        //闭散列：也叫开放定址法，当发生哈希冲突时，如果哈希表未被装满，说明在哈希表中必然还有空位置，那么可以
        //把key存放到冲突位置中的“下一个” 空位置中去。那如何寻找下一个空位置呢？
        //1. 线性探测
        //比如上面的场景，现在需要插入元素44，先通过哈希函数计算哈希地址，下标为4，因此44理论上应该插在该
        //位置，但是该位置已经放了值为4的元素，即发生哈希冲突。
        //线性探测：从发生冲突的位置开始，依次向后探测，直到寻找到下一个空位置为止。
        //插入
        //通过哈希函数获取待插入元素在哈希表中的位置
        //如果该位置中没有元素则直接插入新元素，如果该位置中有元素发生哈希冲突，使用线性探测找到
        //下一个空位置，插入新元素

        //2. 二次探测
        //采用闭散列处理哈希冲突时，不能随便物理删除哈希表中已有的元素，若直接删除元素会影响其他
        //元素的搜索。比如删除元素4，如果直接删除掉，44查找起来可能会受影响。因此线性探测采用标
        //记的伪删除法来删除一个元素。
        //线性探测的缺陷是产生冲突的数据堆积在一块，这与其找下一个空位置有关系，因为找空位置的方式就是挨
        //着往后逐个去找，因此二次探测为了避免该问题，找下一个空位置的方法为：Hi = (H0 + i^2 )% m, 或者：Hi = (H0 - i^2)% m。
        // 其中：i = 1,2,3…(第几次冲突)， 是通过散列函数Hash(x)对元素的关键码 key 进行计算得到的位置，
        //m是表的大小。 对于2.1中如果要插入44，产生冲突，使用解决后的情况为：

        //研究表明：当表的长度为质数且表装载因子a不超过0.5时，新的表项一定能够插入，而且任何一个位置都不
        //会被探查两次。因此只要表中有一半的空位置，就不会存在表满的问题。在搜索时可以不考虑表装满的情
        //况，但在插入时必须确保表的装载因子a不超过0.5，如果超出必须考虑增容。
        //因此：比散列最大的缺陷就是空间利用率比较低，这也是哈希的缺陷。

        // 所以当冲突率达到一个无法忍受的程度时，我们需要通过降低负载因子来变相的降低冲突率。
        //已知哈希表中已有的关键字个数是不可变的，那我们能调整的就只有哈希表中的数组的大小。


        // ****5.8 冲突-解决-开散列/哈希桶（重点掌握） ****
        //开散列法又叫 链地址法(开链法)， 首先对关键码集合用散列函数计算散列地址，具有相同地址的关键码归于同一子
        //集合，每一个子集合称为一个桶，各个桶中的元素通过一个 单链表 链接起来，各链表的头结点存储在哈希表中。
        // 开散列，可以认为是把一个在大集合中的搜索问题转化为在小集合中做搜索了。

        // 是 数组+链表+红黑树的组织
        // 当链表长度>=8 && 数组长度 >= 64时
        // 此时会变成红黑树
        // 时间复杂度是O(1) 因为把链表的长度看作是一个常数


        //5.9 冲突严重时的解决办法
        //刚才我们提到了，哈希桶其实可以看作将大集合的搜索问题转化为小集合的搜索问题了，那如果冲突严重，就意味
        //着小集合的搜索性能其实也时不佳的，这个时候我们就可以将这个所谓的小集合搜索问题继续进行转化，例如：
        //1. 每个桶的背后是另一个哈希表
        //2. 每个桶的背后是一棵搜索树

        // 5.11 性能分析
        //虽然哈希表一直在和冲突做斗争，但在实际使用过程中，我们认为哈希表的冲突率是不高的，冲突个数是可控的，
        //也就是每个桶中的链表的长度是一个常数，所以，通常意义下，我们认为哈希表的插入/删除/查找时间复杂度是O(1) 。
        //5.12 和 java 类集的关系
        //1. HashMap 和 HashSet 即 java 中利用哈希表实现的 Map 和 Set
        // 2. java 中使用的是哈希桶方式解决冲突的
        //3. java 会在冲突链表长度大于一定阈值后，将链表转变为搜索树（红黑树）
        //4. java 中计算哈希值实际上是调用的类的 hashCode 方法，进行 key 的相等性比较是调用 key 的 equals 方
        //法。所以如果要用自定义类作为 HashMap 的 key 或者 HashSet 的值，必须覆写 hashCode 和 equals 方法，
        // 而且要做到 equals 相等的对象，hashCode 一定是一致的。

    }
    public static void main5(String[] args) {
        // ****4. Set 的说明 ****
        //Set 的官方文档
        //Set与Map主要的不同有两点：Set是继承自Collection的接口类， Set中只存储了Key。
        Set<String> set = new TreeSet<>();
        set.add("hello");
        set.add("abc");
        set.add("hello");
        System.out.println(set); // 输出的时候只能有 hello abc 两个值 因为最后一个hello重复了不打印
        // 因为 TreeSet 的底层运用了 TreeMap 所以不能重复

        // 常见方法说明
        //方法解释
        //boolean add(E e)添加元素，但重复元素不会被添加成功
        //void clear()清空集合
        //boolean contains(Object o)判断 o 是否在集合中
        //Iterator<E> iterator()返回迭代器
        //boolean remove(Object o)删除集合中的 o
        // int size()返回set中元素的个数
        //boolean isEmpty()检测set是否为空，空返回true，否则返回false
        // Object[] toArray()将set中的元素转换为数组返回
        //boolean containsAll(Collection<?> c)集合c中的元素是否在set中全部存在，是返回true，否则返回
        //false
        // boolean addAll(Collection<? extends
        //E> c)将集合c中的元素添加到set中，可以达到去重的效果


        //注意：
        //1. Set是继承自Collection的一个接口类
        //2. Set中只存储了key，并且要求key一定要唯一
        //3. TreeSet的底层是使用Map来实现的，其使用key与Object的一个默认对象作为键值对插入到Map中的
        //4. ***Set最大的功能就是对集合中的元素进行去重*** 因为集合当中的内容是不重复的
        //5. 实现Set接口的常用类有TreeSet和HashSet，还有一个LinkedHashSet，LinkedHashSet是在HashSet的基础
        //上维护了一个双向链表来记录元素的插入次序。
        //6. Set中的Key不能修改，如果要修改，先将原来的删除掉，然后再重新插入
        //7. TreeSet中不能插入null的key，HashSet可以。
    }

    public static void main4(String[] args) {
        Map<Student,Integer> treeMap = new TreeMap<>();
       /* treeMap.put(new Student(),1); // 这里面是会报错的
                                        // 因为这里的 key 是不能比较的
        treeMap.put(new Student(),4); */
        System.out.println(treeMap);
    }
    public static void main3(String[] args) {
        // **** 3. Map 的使用 ****

        //3.1 关于Map的说明
        //Map是一个接口类，该类没有继承自Collection，该类中存储的是<K,V>结构的键值对，并且K一定是唯一的，不
        //能重复。


        // Map 常用的方法介绍
        Map<String,Integer> treeMap = new TreeMap<>(); //TreeMap 底层是一个红黑树
        //   key    value
        // 放元素
        treeMap.put("hello",4);
        treeMap.put("abc",3);
        treeMap.put("size",8);
        // 这里面比较大小的方式是用key来比较
        System.out.println(treeMap);

        // 获取元素
       Integer val =   treeMap.get("hello");
        System.out.println(val);
        // 删除
        treeMap.remove("hello");
        System.out.println(treeMap);
        // Set （是一个纯key的模型） 返回所有key值不重复的集合
        Set<String> keySet = treeMap.keySet(); // 这一行就会把Map里面所有的key放到Set里面
        // 但是放进去的值顺序有可能是错的

         // 返回所有value的可重复集合
        //Collection<V>
        Collection<Integer> collection = treeMap.values();
        System.out.println(collection);

        //3.2 关于Map.Entry<K, V>的说明
        //Map.Entry<K, V> 是Map内部实现的用来存放<key, value>键值对映射关系的内部类，该内部类中主要提供了
        //<key, value>的获取，value的设置以及Key的比较方式。
        // ***entrySet方法***
        //Set<Map.Entry<E,V>> entrySet() // 返回所有的 key-value 映射关系

        Set<Map.Entry<String,Integer>> set = treeMap.entrySet();
        // 循环遍历
        for (Map.Entry<String,Integer> entry : set) {
            System.out.println("key:"+entry.getKey()+" val:"+entry.getValue());
        }
        // Map 是不能够用迭代器遍历的
       // System.out.println(set);

        // 注意：
        //1. Map是一个接口，不能直接实例化对象，如果要实例化对象只能实例化其实现类TreeMap或者HashMap
        // 2. Map中存放键值对的Key是唯一的，value是可以重复的
        //3. 在TreeMap中插入键值对时，key不能为空，否则就会抛NullPointerException异常，value可以为空。但
        //是HashMap的key和value都可以为空。
        //4. Map中的Key可以全部分离出来，存储到Set中来进行访问(因为Key不能重复)。
        //5. Map中的value可以全部分离出来，存储在Collection的任何一个子集合中(value可能有重复)。
        //6. Map中键值对的Key不能直接修改，value可以修改，如果要修改key，只能先将该key删除掉，然后再来进行
        //重新插入。



    }
    public static void main2(String[] args) {
        //2.1 概念及场景
        //Map和set是一种专门用来进行搜索的容器或者数据结构，其搜索的效率与其具体的实例化子类有关。以前常见的
        //搜索方式有：
        //1. 直接遍历，时间复杂度为O(N)，元素如果比较多效率会非常慢
        //2. 二分查找，时间复杂度为,但搜索前必须要求序列是有序的
        //上述排序比较适合静态类型的查找，即一般不会对区间进行插入和删除操作了，而现实中的查找比如：
        //1. 根据姓名查询考试成绩
        //2. 通讯录，即根据姓名查询联系方式
        //3. 不重复集合，即需要先搜索关键字是否已经在集合中
        //可能在查找时进行一些插入和删除的操作，即动态查找，那上述两种方式就不太适合了，本节介绍的Map和Set是
        //一种适合动态查找的集合容器。
        //2.2 模型
        //一般把搜索的数据称为关键字（Key），和关键字对应的称为值（Value），将其称之为Key-value的键值对，所以
        //模型会有两种：
        //1. 纯 key 模型，比如：
        //有一个英文词典，快速查找一个单词是否在词典中
        //快速查找某个名字在不在通讯录中
        //2. Key-Value 模型，比如：
        //统计文件中每个单词出现的次数，统计结果是每个单词都有与其对应的次数：<单词，单词出现的次数>
        //梁山好汉的江湖绰号：每个好汉都有自己的江湖绰号
        //而Map中存储的就是key-value的键值对，Set中只存储了Key。
    }
    public static void main1(String[] args) {
        System.out.println("Hello world!");
        // ****Map和Set****

        //【本节目标】
        //1. 掌握 Map/Set 及实际实现类 HashMap/TreeMap/HashSet/TreeSet 的使用
        //2. 掌握 HashMap 和 HashSet 背后的数据结构哈希表的原理和简单实现

        // 1. 搜索树

        //1.1 概念
        //二叉搜索树又称二叉排序树，它或者是一棵空树，或者是具有以下性质的二叉树:
        //若它的左子树不为空，则左子树上所有节点的值都小于根节点的值
        //若它的右子树不为空，则右子树上所有节点的值都大于根节点的值
        //它的左右子树也分别为二叉搜索树

        //1.2 操作-查找

        //1.3 操作-插入
        BinarySearchTree binarySearchTree = new BinarySearchTree();
        binarySearchTree.insert(2);
        binarySearchTree.insert(1);
        binarySearchTree.insert(5);
        binarySearchTree.insert(7);
        binarySearchTree.insert(55);
        binarySearchTree.inorder(binarySearchTree.root);
        System.out.println();
        BinarySearchTree.TreeNode ret = binarySearchTree.find(5);
        System.out.println("找到了"+ret.val);

        //1.4操作-删除
        //设待删除结点为 cur, 待删除结点的双亲结点为 parent
    // 1. cur.left == null
        // 1. cur 是 root，则 root = cur.right
        // 2. cur 不是 root，cur 是 parent.left，则 parent.left = cur.right
        // 3. cur 不是 root，cur 是 parent.right，则 parent.right = cur.right
    // 2. cur.right == null
        // 1. cur 是 root，则 root = cur.left
        //2. cur 不是 root，cur 是 parent.left，则 parent.left = cur.left
        //3. cur 不是 root，cur 是 parent.right，则 parent.right = cur.left
    // 3. cur.left != null && cur.right != null (这个是最难删除的)
        // 1. 需要使用替换法进行删除，即在它的右子树中寻找中序下的第一个结点(关键码最小)，用它的值填补到被
        //删除节点中，再来处理该结点的删除问题

        //删除节点测试
        binarySearchTree.remove(5);
        binarySearchTree.inorder(binarySearchTree.root);
        System.out.println();


        // 1.6 性能分析
        //插入和删除操作都必须先查找，查找效率代表了二叉搜索树中各个操作的性能。
        //对有n个结点的二叉搜索树，若每个元素查找的概率相等，则二叉搜索树平均查找长度是结点在二叉搜索树的深度
        //的函数，即结点越深，则比较次数越多。
        //但对于同一个关键码集合，如果各关键码插入的次序不同，可能得到不同结构的二叉搜索树：
        //最优情况下，二叉搜索树为完全二叉树，其平均比较次数为：
        //最差情况下，二叉搜索树退化为单支树，其平均比较次数为：
        //问题：如果退化成单支树，二叉搜索树的性能就失去了。那能否进行改进，不论按照什么次序插入关键码，都可以
        //是二叉搜索树的性能最佳？
        //1.7 和 java 类集的关系
        //TreeMap 和 TreeSet 即 java 中利用搜索树实现的 Map 和 Set；实际上用的是红黑树，而红黑树是一棵近似平衡的
        //二叉搜索树，即在二叉搜索树的基础之上 + 颜色以及红黑树性质验证，关于红黑树的内容后序再进行讲解。




    }
}